package com.weilx959.spt.core.config;

import com.weilx959.spt.core.component.FormItemControl;
import com.weilx959.spt.core.utils.Try;
import javafx.beans.DefaultProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.RowConstraints;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 表单控件，支持自动布局
 *
 * @author weilx
 * @since 2024-05-28
 */
@Slf4j
@DefaultProperty("controls")
@SuppressWarnings({"unchecked", "rawtypes"})
public class FormControlConfig extends GridPane implements Config {
    private static final int MIN_COLUMN_WIDTH = 210;
    private static final int PREF_COLUMN_WIDTH = 360;
    private static final int MAX_COLUMN_WIDTH = MIN_COLUMN_WIDTH << 1;
    private static final int TOP_PADDING = 20;
    private static final int BOTTOM_PADDING = 20;
    private static final int LEFT_PADDING = 50;
    private static final int RIGHT_PADDING = 50;
    public static final int ROW_HEIGHT = 50;
    private ObjectProperty<ObservableList<FormItemControl>> controls;

    /**
     * 表单列数，默认0. 0表示自动计算列数
     */
    @Getter
    @Setter
    private int columns;

    private Map<String, String> initConfigMap;

    public FormControlConfig() {
        this.setHgap(50);
        this.setVgap(10);
        this.setPadding(new Insets(TOP_PADDING, RIGHT_PADDING, BOTTOM_PADDING, LEFT_PADDING));

        // width listener
        this.widthProperty().addListener((observable, oldValue, newValue) -> {
            if (Objects.isNull(this.getParent()) || CollectionUtils.isEmpty(this.getChildren())) {
                return;
            }
            if (this.getColumnCount() != calculateColumns(newValue.intValue())) {
                this.resize();
            }
        });
    }

    public ObservableList<FormItemControl> getControls() {
        return controlsProperty().get();
    }

    public ObjectProperty<ObservableList<FormItemControl>> controlsProperty() {
        if (Objects.isNull(controls)) {
            controls = new SimpleObjectProperty<>(this, "controls", FXCollections.observableArrayList());
        }
        return controls;
    }

    public void setControls(ObservableList<FormItemControl> controls) {
        controlsProperty().set(controls);
    }

    @Override
    public Try<Map<String, String>> submit() {
        Map<String, String> configMap = new HashMap<>();

        if (CollectionUtils.isNotEmpty(getControls())) {
            // 先全部校验
            for (FormItemControl control : getControls()) {
                Try<String> valid = control.validate();

                if (valid.isFailure()) {
                    return new Try.Failure<>(valid.getCause());
                }
            }
            // 再提交
            for (FormItemControl control : getControls()) {
                control.submit();
                configMap.put(control.getKey(), control.serialize(control.getValue()));
            }
        }
        this.initConfigMap = null; // 表单提交后，清空initConfigMap，以便下次重新布局
        return new Try.Success<>(configMap);
    }

    @Override
    public void reset() {
        if (CollectionUtils.isNotEmpty(getControls())) {
            for (FormItemControl control : getControls()) {
                control.reset();
            }
        }
    }

    @Override
    public boolean modified() {
        if (CollectionUtils.isNotEmpty(getControls())) {
            for (FormItemControl control : getControls()) {
                if (control.isModified()) {
                    return true;
                }
            }
        }
        return false;
    }

    public FormControlConfig addControl(FormItemControl<?> control) {
        getControls().add(control);
        return this;
    }

    @Override
    public Map<String, String> layout(Map<String, String> configMap) {
        log.info("layout config {} : {}", this.hashCode(), this.getControls());
        this.resize();
        if (Objects.nonNull(this.initConfigMap)) { // 第一次调用layout方法时，configMap为null
            return this.initConfigMap;
        }
        if (CollectionUtils.isEmpty(getControls())) {
            return Collections.emptyMap();
        }
        if (Objects.isNull(configMap)) {
            configMap = new HashMap<>();
        }
        this.initConfigMap = new HashMap<>();

        // 设置默认值
        for (FormItemControl control : getControls()) {
            String value = configMap.get(control.getKey());

            if (StringUtils.isNotBlank(value)) {
                this.initConfigMap.put(control.getKey(), value);
                control.setValue(control.deserialize(value));
                control.submit();
            }
        }
        return this.initConfigMap;
    }

    @Override
    public Map<String, Object> deserialize(Map<String, String> configMap) {
        if (CollectionUtils.isEmpty(getControls()) || MapUtils.isEmpty(configMap)) {
            return Collections.emptyMap();
        }
        Map<String, Object> tmpMap = new HashMap<>();

        for (FormItemControl formItem : getControls()) {
            tmpMap.put(formItem.getKey(), formItem.deserialize(configMap.get(formItem.getKey())));
        }
        return tmpMap;
    }

    @Override
    public Node getView() {
        return this;
    }

    /**
     * 动态布局
     */
    public void resize() {
        this.getChildren().clear();
        this.getRowConstraints().clear();
        // 计算总列数
        int totalColumn = calculateColumns((int)this.getWidth());
        this.createColumnConstraints(totalColumn);
        int rowIndex = 0;
        int columnIndex = 0;
        int lastRowIndex = 0;
        this.addOneRow();

        for (FormItemControl control : getControls()) {
            // 此元件占用的列数: 1 <= colspan <= totalColumn
            int colspan = Math.min(totalColumn, Math.max(1, control.getColspan()));
            // 此元件占用的行数: 1 <= rowspan
            int rowspan = Math.max(1, control.getRowspan());

            if (Objects.isNull(control.getControl())) {
                continue;
            }
            // 找到可以放置的(columnIndex,rowIndex)坐标
            while (columnIndex + colspan > totalColumn) {
                this.addOneRow();

                if (++rowIndex >= lastRowIndex) {
                    columnIndex = 0;
                }
            }
            // 补充多行
            while (rowIndex + rowspan > this.getRowCount()) {
                this.addOneRow();
            }
            this.add(control.getControl(), columnIndex, rowIndex, colspan, rowspan);
            columnIndex += colspan;
            lastRowIndex = rowIndex + rowspan;
        }
    }

    /**
     * 计算列数
     *
     * @param maxWidth 最大宽度
     * @return 列数
     */
    private int calculateColumns(int maxWidth) {
        if (this.columns > 0) {
            return columns;
        }
        maxWidth -= LEFT_PADDING;
        maxWidth -= RIGHT_PADDING;
        return Math.max(1, maxWidth / MAX_COLUMN_WIDTH);
    }

    /**
     * 创建列约束
     *
     * @param columns 列数
     */
    private void createColumnConstraints(int columns) {
        this.getColumnConstraints().clear();
        for (int i = 0; i < columns; i++) {
            this.getColumnConstraints().add(new ColumnConstraints(MIN_COLUMN_WIDTH, PREF_COLUMN_WIDTH, Double.MAX_VALUE,
                Priority.ALWAYS, HPos.CENTER, true));
        }
    }

    private void addOneRow() {
        this.getRowConstraints().add(new RowConstraints(ROW_HEIGHT, ROW_HEIGHT, ROW_HEIGHT,
            Priority.NEVER, VPos.CENTER, false));
    }

    // builder
    public static FormConfigBuilder builder() {
        return new FormConfigBuilder();
    }

    public static class FormConfigBuilder {
        private ObservableList<FormItemControl> controls;

        public FormConfigBuilder controls(ObservableList<FormItemControl> formControls) {
            this.controls = formControls;
            return this;
        }

        public FormConfigBuilder addControl(FormItemControl<?> control) {
            if (Objects.isNull(controls)) {
                controls = FXCollections.observableArrayList();
            }
            controls.add(control);
            return this;
        }

        public FormControlConfig build() {
            FormControlConfig formControlConfig = new FormControlConfig();
            formControlConfig.setControls(controls);
            return formControlConfig;
        }
    }
}
